package model.webService;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.SortedMap;

import org.json.JSONException;

import model.OnScreen;
import model.service.JSONBehaviour;
import model.service.ServiceBehaviour;
import view.Scanner;
import view.Splash;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Xml.Encoding;
import android.widget.Toast;

public class ClientThread implements Runnable 
{
	private Command lastCommand = Command.Login;
	
	private final String TAG = ClientThread.class.getSimpleName();
	
	private static final int SERVERPORT = 1000;
	private static final String SERVER_IP = "192.168.30.231";	
	private static final String machineName = "Android";
	private static final int BLOCKSIZE = 128;
	
	private Socket socket;

	private Splash _splash;
	
	public ClientThread(Splash splashScreen) 
	{
		_splash = splashScreen;
	}

	@Override
	public void run() 
	{
		try
        {
        	Log.d("WEBSERVICE", "before creating socket");
        	InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
			socket = new Socket(serverAddr, SERVERPORT);
			
			sendSocketCall(Command.Login);
        	
            if(socket != null)
            {
            	// keep listening
            	while(socket.isConnected() && !socket.isClosed())
            	{
            		DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());  
            		
            		readResponse(dataInputStream);
            	}
            	Log.e(TAG, "Socket disconnected");
            }
        } catch (UnknownHostException e1) {
            e1.printStackTrace();
        } catch (ConnectException e){
        	_splash.makeToast("Unable to connect to server");
        	OnScreen.getInstance().getWebServiceConnector().connectToServer(_splash);
        } catch (IOException e1) {
            e1.printStackTrace();
        } catch (Exception e) {
			e.printStackTrace();
        }
	}
		
	void sendSocketCall(Command command)
	{
		sendSocketCall(command, "");
	}
	
	void sendSocketCall(Command command, String msg)
	{
		try 
		{
			if(socket != null)
			{
				OutputStream out = socket.getOutputStream(); 
				DataOutputStream dos = new DataOutputStream(out);
				
				int cmd = command.ordinal();
				String message = String.format("%d,%s,%s|", cmd, machineName, msg); 
				
				String empty = "";
				
				byte[] messageBytes = empty.getBytes();
		        ByteBuffer strNameBuffer = ByteBuffer.allocate(2*message.length());
		        strNameBuffer.order(ByteOrder.LITTLE_ENDIAN);
		        if (message != null)
		        {
		        	for(int x = 0; x < message.length(); x++)
		        	{
		        		strNameBuffer.putChar(message.charAt(x));
		        	}
		        	messageBytes = strNameBuffer.array();
		        }
		        
		        Log.i(TAG, String.format("sending message: \"%s\" - - %d", message, messageBytes.length));
		        
				dos.write(messageBytes, 0, messageBytes.length);
				
				lastCommand = command;
			}
			else
			{
				Log.e(TAG, "Socket == null");
//				InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
//				socket = new Socket(serverAddr, SERVERPORT);
//				
//				sendSocketCall(Command.Login);
			}
		} catch (SocketException e){
			Log.e(TAG, String.format(""));
			OnScreen.getInstance().getWebServiceConnector().connectToServer(_splash);
		} catch (IOException e) { 
			Log.e(TAG, e.toString());
			e.printStackTrace();
		}
	}
	
	private void readResponse(DataInputStream dataInputStream)
	{
		try
		{
			byte[] data = new byte[4];  
        	dataInputStream.read(data, 0, data.length);
        	
        	String sizeString = new String(data);
        	int sizeOfMessage = Integer.parseInt(sizeString);
        	
        	Log.e(TAG, String.format("size: %s", sizeString));
        	
        	int received = 0;
        	String message = "";
        	
        	data = new byte[BLOCKSIZE];
        	        	
        	//dataInputStream.a
//        	ArrayList<Byte> allBytes = new ArrayList<Byte>();
        	
        	byte[] allBytes = new byte[sizeOfMessage];
        	//ByteBuffer buffer = ByteBuffer.allocate(sizeOfMessage);
        			
        	while(received < sizeOfMessage)
        	{ 
        		Log.i(TAG, String.format("received: %d -- size - received = %d", received, (sizeOfMessage - received)));
//				String appender = "";
			    if((sizeOfMessage - received) < BLOCKSIZE) 
			    {			    				    		
				    	data = new byte[(sizeOfMessage - received)];
				    	dataInputStream.read(data);
//				    	appender = new String(data, Charset.forName("UTF-8"));
				    	
//				    	Log.d(TAG, "msg2: " + appender);
				    	
//					    message += appender;
					    
					    System.arraycopy(data, 0, allBytes, received, data.length);
					    
				    	received += (sizeOfMessage - received);
			    } 
			    else
			    { 
			    	data = new byte[BLOCKSIZE];
			    	dataInputStream.read(data, 0, BLOCKSIZE);
//			    	appender = new String(data, Charset.forName("UTF-8"));
			    	
//			    	Log.d(TAG, "msg1: " + appender);
			    	
//				    message += appender;
				    
				    System.arraycopy(data, 0, allBytes, received, data.length);
				    
			    	received += BLOCKSIZE;
			    } 
//			    buffer.put(data);
			    
			}
        	
        	ByteBuffer wrapper = ByteBuffer.allocate(allBytes.length + dataInputStream.available());
        	wrapper.put(allBytes);
        	
//        	ByteBuffer wrapper = ByteBuffer.wrap(allBytes);
        	if(lastCommand == Command.AllSnippets)
        	{
	        	while(dataInputStream.available() > 0)
	        	{
	        		wrapper.put(dataInputStream.readByte());       		
	        	}
        	}
        	wrapper.order(ByteOrder.LITTLE_ENDIAN);
//        	CharBuffer charred = wrapper.asCharBuffer();
        	message = new String(wrapper.array(), "UTF-8");
        	
        	Log.d(TAG, String.format("size: %d\n%s", message.length(), message.substring(0, message.length() / 2)));
        	Log.d(TAG, String.format("%s", message.substring(message.length() / 2)));
        	message = message.replaceAll("[\u0000-\u001f]", "");
        	Log.e(TAG, "------------------------");
//        	charsets.clear();
        	Log.i(TAG, String.format("size: %d\n%s", message.length(), message.substring(0, message.length() / 2)));
        	Log.i(TAG, String.format("%s", message.substring(message.length() / 2)));
        	handleReceivedMessage(message);
        	
		} catch (NumberFormatException e){
			//...
		}catch (IOException e) 
		{
			Log.e(TAG, "READ FAIL - "  + e.toString());
		} catch (Exception e) 
		{
			Log.e(TAG, "OTHER FAIL - " + e.toString());
		}
	}        
 
	private void handleReceivedMessage(String message) throws Exception
	{
		if(message.trim().isEmpty())
			throw new Exception(String.format("cmd: \'%s\' returned empty message", lastCommand.toString()));
		
		switch(lastCommand)
    	{
			case Login:
				logIn();
				break;
			case Logout:
				logOut();
				break;
			case AllSnippets:
			case getSnippet:
			case Subscribe:
			case ReceiveUpdate:
			default:
				Log.i(TAG, lastCommand.toString());
				translateSnippets(message);
				break;
    	}
	}
	
	private void logIn()
	{
		sendSocketCall(Command.AllSnippets);
	}
	
	public void logOut() 
	{
		sendSocketCall(Command.Logout);
		
		socket = null;
	}
	
	public void subscribeToSnippet(String snippetID)
	{
		sendSocketCall(Command.Subscribe, snippetID);
	}
		
	public void unSubscribeToSnippet(String snippetID)
	{
		sendSocketCall(Command.Unsubscribe, snippetID);
	}
		
	private void translateSnippets(String message)
	{
		Log.d(TAG, "Translate()");
		
		ServiceBehaviour translator = createTranslator(message);
		try 
		{
			translator.translateResponse(message);
			if(lastCommand == Command.AllSnippets)
				exitSplashScreen();
			lastCommand = Command.ReceiveUpdate;
		} catch (JSONException e) 
		{
			Log.e(TAG, "Invalid JSON");
			
        	_splash.makeToast("Invalid JSON");
        	OnScreen.getInstance().getWebServiceConnector().connectToServer(_splash);
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	private void exitSplashScreen()
	{			
		Handler handler = new Handler(Looper.getMainLooper());
		handler.post(new ViewTransition());	
	}
	
	private class ViewTransition implements Runnable
	{
		@Override
		public void run() 
		{
			Intent intent = new Intent(_splash, Scanner.class);
			_splash.startActivity(intent);
		}
	}
	
	private ServiceBehaviour createTranslator(String result)
	{
		if(result.startsWith("<")) // XML always starts with this token and JSON starts with [
		{
			// XML (temporarily says new JsonBehaviour because the translator has to be initialized in all paths
			return new JSONBehaviour();				
		}
		else
		{
			//JSON
			return new JSONBehaviour();
		}
	}

	public void getSnippet(String id) {
		sendSocketCall(Command.getSnippet, id);
		
	}

	public void handleSubscriptions(String subscribeIds, String unSubscribeIds) 
	{
		if(!unSubscribeIds.trim().isEmpty())
			sendSocketCall(Command.Unsubscribe, unSubscribeIds);
		if(!subscribeIds.trim().isEmpty())
			sendSocketCall(Command.Subscribe, subscribeIds);		
	}
}
